package com.example.asm

import com.android.build.api.instrumentation.AsmClassVisitorFactory
import com.android.build.api.instrumentation.ClassContext
import com.android.build.api.instrumentation.ClassData
import com.android.build.api.instrumentation.InstrumentationParameters
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.commons.AdviceAdapter
import org.objectweb.asm.commons.Method

abstract  class MethodCostTransformer: AsmClassVisitorFactory<InstrumentationParameters.None> {

    override fun isInstrumentable(classData: ClassData): Boolean {
        return true
    }

    override fun createClassVisitor(
        classContext: ClassContext, nextClassVisitor: ClassVisitor
    ): ClassVisitor {
       return MethodCostClassVisitor(Opcodes.ASM9, nextClassVisitor)
    }

    private class MethodCostClassVisitor(api: Int, classVisitor: ClassVisitor) : ClassVisitor(api, classVisitor) {
        override fun visitMethod(
            access: Int,
            name: String?,
            descriptor: String?,
            signature: String?,
            exceptions: Array<out String?>?
        ): MethodVisitor? {
            val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
            return  MethodInvocationVisitor(api, mv, access, name, descriptor)
        }
    }

    private class MethodInvocationVisitor(
        api: Int, mv: MethodVisitor, access: Int, name: String?, descriptor: String?
    ) : AdviceAdapter(api, mv, access, name, descriptor) {
        private val methodName = name
        private var injectTimeCost = false
        override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
            println("descriptor=$descriptor, visible=$descriptor")
            injectTimeCost = (descriptor == "Lcom/example/demo/MethodTimeCost;")
            return super.visitAnnotation(descriptor, visible)
        }

        override fun onMethodEnter() {
            super.onMethodEnter()
            if (injectTimeCost) {
                visitLdcInsn(methodName)
                invokeStatic(
                    Type.getType("Lcom/example/demo/TimeCostRecorder;"), Method("startTime", "(Ljava/lang/String;)V")
                )
            }
        }

        override fun onMethodExit(opcode: Int) {
            super.onMethodExit(opcode)
            if (injectTimeCost) {
                visitLdcInsn(methodName)
                invokeStatic(
                    Type.getType("Lcom/example/demo/TimeCostRecorder;"), Method("endTime", "(Ljava/lang/String;)V")
                )
            }
        }

        override fun visitMaxs(maxStack: Int, maxLocals: Int) {
            super.visitMaxs(maxStack.coerceAtLeast(1), maxLocals)
        }
    }
}